// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dfa.axis.v1_19.matchtables;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * Abstract class for actual SOAP lookup objects.  Handles parceling out IDs in batches,
 * iterator logic, and conversion of String IDs to Long IDs.
 *
 * @author Moshe Kahan
 */
abstract class AbstractSoapLookup<Record> {

  private static final int ID_ARRAY_MAX_SIZE = 200;

  private List<long[]> entityIdList;
  private Iterator<long[]> entityIdListIterator;

  private List<Record> recordArray;
  private Iterator<Record> recordArrayIterator;

  private Record next;

  private boolean makeEmptyIdListSoapCall = false;

  /**
   * Construct SOAP Lookup without any input entity IDs.
   */
  AbstractSoapLookup() {
    makeEmptyIdListSoapCall = true;
    entityIdList = new ArrayList<long[]>();
    entityIdListIterator = entityIdList.iterator();
    recordArray = new ArrayList<Record>();
    recordArrayIterator = recordArray.iterator();
    next = null;
  }

  /**
   * Construct SOAP Lookup with collection of input entity ID Longs that will be parceled out into
   * separate SOAP requests of size ID_ARRAY_MAX_SIZE.
   */
  AbstractSoapLookup(Collection<Long> entityIdCollection) {
    init(entityIdCollection);
  }

  // Group input entity IDs into sub-groups SOAP can handle.
  private void init (Collection<Long> entityIdCollection) {
    entityIdList = new ArrayList<long[]>();

    int i = 0;
    int entityIdCollectionSize = entityIdCollection.size();
    Iterator<Long> iterator = entityIdCollection.iterator();
    while (iterator.hasNext()) {
      int entitiesLeft = entityIdCollectionSize - i;
      int subArrayCount = entitiesLeft > ID_ARRAY_MAX_SIZE ? ID_ARRAY_MAX_SIZE : entitiesLeft;
      long[] subArray = new long[subArrayCount];
      for (int j = 0; j < subArray.length;) {
        subArray[j++] = iterator.next();
        ++i;
      }
      entityIdList.add(subArray);
    }
    entityIdListIterator = entityIdList.iterator();

    recordArray = new ArrayList<Record>();
    recordArrayIterator = recordArray.iterator();
  }

  /**
   * Actually do a SOAP lookup on this sub-set of IDs. Each subclass implements differently
   * depending on page-able or non-page-able results.
   *
   * @param ids set of IDs to retrieve entities for.
   */
  abstract void executeRequest(long[] ids) throws DfaSoapException;

  /**
   * Get the next entity if available. Performs SOAP lookup if necessary.
   */
  boolean hasNext() throws DfaSoapException {
    if (next == null) {
      next = getNext();
    }
    return next != null;
  }

  /**
   * Retrieves the next entity if available. Performs SOAP lookup if necessary.
   */
  Record next() throws DfaSoapException {
    if (next == null) {
      next = getNext();
    }

    if (next != null) {
      Record returnValue = next;
      next = null;
      return returnValue;
    }

    return null;
  }

  // Actually retrieves the next entity by checking if there are more records retrieved or
  // more IDs that we can lookup. Some requests have no input IDs.
  private Record getNext() throws DfaSoapException {
    // See if we need to populate recordArray
    if (!recordArrayIterator.hasNext()) {
      // For makeEmptyIdListSoapCall call executeRequest once.
      // Then subsequent request will only use recordArrayIterator.
      if (makeEmptyIdListSoapCall) {
        executeRequest(null);
        makeEmptyIdListSoapCall = false;
      } else if (entityIdListIterator.hasNext()) {
        executeRequest(entityIdListIterator.next());
      }
    }

    if (recordArrayIterator.hasNext()) {
      return recordArrayIterator.next();
    }
    return null;
  }

  protected void clearRecordArray() {
    recordArray.clear();
  }

  protected void addRecords(List<Record> subRecordArray) {
    recordArray.addAll(subRecordArray);
  }

  protected void resetRecordArrayIterator() {
    recordArrayIterator = recordArray.iterator();
  }
}
